import { UploadProps } from 'antd';
import { RcFile } from 'antd/es/upload';
import { HmacSHA1, enc } from 'crypto-js';
import React, { useContext, useMemo } from 'react';
import { YmFilePathRule } from './YmAliOssPathRules';
import { AliOssContextBaseValue, YmFilePathContextType } from './interface';
type AliOssProviderValue = AliOssContextBaseValue & YmFilePathContextType;

const YmFilePathContext = React.createContext<AliOssProviderValue>(
  {} as AliOssProviderValue,
);

const defaultpolicy = {
  expiration: '2120-01-01T12:00:00.000Z',
  conditions: [['content-length-range', 0, 104857600]],
};

const YmAliOssFilePathRuleProvider = ({
  children,
  value,
}: React.PropsWithChildren<{
  value: AliOssProviderValue;
}>) => {
  return (
    <YmFilePathContext.Provider value={value}>
      {children}
    </YmFilePathContext.Provider>
  );
};

function _useAliOss(
  key: string | ((file: RcFile | string | Blob) => string),
): NonNullable<UploadProps['customRequest']> {
  const {
    accessKeyId,
    accessKeySecret,
    policy = defaultpolicy,
    region,
    bucket,
  } = useContext(YmFilePathContext);

  const aliOssUrl = `https://${bucket}.${region}.aliyuncs.com/`;

  const policyB64 = useMemo(() => {
    if (typeof policy === 'string') {
      return window.btoa(policy);
    } else {
      try {
        return window.btoa(JSON.stringify(policy));
      } catch (e) {
        return '';
      }
    }
  }, [policy]);

  const Signature = useMemo(() => {
    return HmacSHA1(policyB64, accessKeySecret).toString(enc.Base64);
  }, [policyB64, accessKeySecret]);

  return function ({
    file,
    data,
    filename,
    headers = {},
    method = 'POST',
    withCredentials,
    onProgress,
    onSuccess,
    onError,
  }) {
    // 不使用外面的东西
    const action = aliOssUrl;
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    const publish = function () {
      xhr.open(method, action, true);
      xhr.send(formData);
    };
    formData.set('OSSAccessKeyId', accessKeyId);
    formData.set('policy', policyB64);
    formData.set('Signature', Signature);
    if (typeof key === 'string') {
      formData.set('key', key);
    } else {
      formData.set('key', key(file));
    }

    if (data) {
      Object.keys(data).forEach((key) => {
        const value = data[key];
        if (Array.isArray(value)) {
          value.forEach((item) => {
            formData.append(`${key}[]`, item);
          });
          return;
        }
        formData.append(key, value as string | Blob);
      });
    }
    if (file instanceof Blob) {
      formData.append(filename ?? (file as any).name, file, (file as any).name);
    } else {
      formData.append(filename ?? file, file);
    }

    if (withCredentials) {
      xhr.withCredentials = true;
    }

    if (onProgress && xhr.upload) {
      xhr.upload.onprogress = function progress(e) {
        if (e.total > 0) {
          Object.assign(e, {
            percent: Number(((e.loaded / e.total) * 100).toFixed(2)),
          });
        }
        onProgress(e);
      };
    }

    xhr.onerror = function error(e) {
      onError?.(e, publish);
    };

    xhr.onload = function onload() {
      const text = xhr.response || xhr.responseText;
      const body =
        xhr.responseType === 'json' && text ? JSON.parse(text) : text;
      if (xhr.status < 200 || xhr.status >= 300) {
        const msg = `cannot ${method} ${action} ${xhr.status}'`;
        const err = new Error(msg) as any;
        err.status = xhr.status;
        err.method = method;
        err.url = action;
        onError?.(err, publish);
        return;
      } else {
        const key = formData.get('key');
        if (key) {
          onSuccess?.(action + key, xhr);
        } else {
          onSuccess?.(body, xhr);
        }
      }
    };
    Object.keys(headers).forEach((h) => {
      if (headers[h] !== null) {
        xhr.setRequestHeader(h, headers[h]);
      }
    });
    publish();
  };
}

function useYmAliOss(
  busPath?: string,
): NonNullable<UploadProps['customRequest']> {
  const {
    phone,
    client,
    system,
    busPath: _busPath,
  } = useContext(YmFilePathContext);
  if (!client || !system) {
    throw new Error(
      '请在使用useYmAliOss前配置YmAliOssFilePathRuleProvider所需要的属性',
    );
  }
  if (!phone) {
    console.warn('当前暂未获取到用户的手机号,如有问题请主动排查');
  }
  const _customRequest = _useAliOss((file) => {
    return YmFilePathRule({
      client,
      ext: file instanceof File ? file.name.split('.').pop() : undefined,
      phone,
      busPath: busPath ? busPath : _busPath,
      system,
    });
  });
  return (...params) => {
    return _customRequest(...params);
  };
}

export { YmAliOssFilePathRuleProvider, useYmAliOss };
export type { AliOssProviderValue };
